﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ObjectTransposeApi.Structure;
using System.Reflection;
using ObjectTransposeApi.Helper;
using System.Collections;

namespace ObjectTransposeApi.Handler.Property
{
    /// <summary/>
    internal class ListPropertyHandler : AbstractPropertyHandler
    {
        /// <summary/>
        internal override IEnumerable<QueueStructure> Process(PropertyStructure propertyStructure)
        {
            // is the destination a generic type ... check before calling get generic type defition
            if (!propertyStructure.DestinationPropertyInfo.PropertyType.IsGenericType)
            {
                return base._successor.Process(propertyStructure);
            }

            // is this a generic list type
            if (propertyStructure.DestinationPropertyInfo.PropertyType.GetGenericTypeDefinition() != typeof(List<>))
            {
                return base._successor.Process(propertyStructure);
            }

            // get the list of generic parameter types
            Type[] destinationListGenericParameterTypes = propertyStructure.DestinationPropertyInfo.PropertyType.GetGenericArguments();

            // there will be one type because we already know this is a list
            Type destinationListGenericParameterType = destinationListGenericParameterTypes[0];

            // fetch the list
            object destinationList = propertyStructure.DestinationPropertyInfo.GetValue(
                propertyStructure.Destination, 
                null);

            // if the list is not set then create it
            if (destinationList == null)
            {
                // create it
                destinationList = Activator.CreateInstance(
                    propertyStructure.DestinationPropertyInfo.PropertyType);

                // set the property
                propertyStructure.DestinationPropertyInfo.SetValue(
                    propertyStructure.Destination,
                    destinationList, 
                    null);
            }

            // read the source property value .. do it after the list has been created
            object rawSourceValue = propertyStructure.SourcePropertyInfo.GetValue(
                propertyStructure.Source,
                null);

            // if null then move on
            if (rawSourceValue == null)
            {
                return base._successor.Process(propertyStructure);
            }

            // fetch the add method from the generic list
            MethodInfo destinationListAddMethodInfo = propertyStructure.DestinationPropertyInfo.PropertyType.GetMethod(
                Constant.METHOD_ADD,
                new Type[] { destinationListGenericParameterType });

            // fetch the constructor with no parameters
            ConstructorInfo destinationConstructorInfo = ReflectionHelper.FetchConstructor(
                destinationListGenericParameterType,
                new Type[] { });

            List<QueueStructure> queueStructures = new List<QueueStructure>();

            // expand the source 
            foreach (object sourceValue in propertyStructure.ExpandCallback(rawSourceValue))
            {
                // add the source adn destination the same tpye
                if (destinationListGenericParameterType == sourceValue.GetType())
                {
                    // were indexers providerd
                    if (propertyStructure.IndexMapper != null)
                    {
                        // does the object have the same index
                        ResultHelper resultHelper = ListPropertyHandler.FetchMatchingIndexValue(
                            destinationList,
                            destinationListGenericParameterType,
                            sourceValue,
                            propertyStructure.IndexMapper.IndexName);

                        // if no matches then exit
                        if (!resultHelper.Success)
                        {
                            continue;
                        }

                        // if found then copy
                        if (resultHelper.Value != null)
                        {
                            // add to the queue
                            queueStructures.Add(new QueueStructure(
                                resultHelper.Value,
                                ReflectionHelper.FetchGetSetProperties(resultHelper.Value.GetType()),
                                sourceValue,
                                ReflectionHelper.FetchGetSetProperties(sourceValue.GetType())));
                     
                            // move on
                            continue;
                        }
                    }

                    // add the source to the list
                    destinationListAddMethodInfo.Invoke(destinationList, new object[] { sourceValue });

                    // move on
                    continue;
                }
                 
                // nothing to do if the constructor is missing
                if (destinationConstructorInfo == null)
                {
                    continue;
                }

                // were indexers providerd
                if (propertyStructure.IndexMapper != null)
                {
                    // does the object have the same index
                    ResultHelper resultHelper = ListPropertyHandler.FetchMatchingIndexValue(
                        destinationList,
                        destinationListGenericParameterType,
                        sourceValue,
                        propertyStructure.IndexMapper.IndexName);

                    // if no matches then exit
                    if (!resultHelper.Success)
                    {
                        continue;
                    }

                    // if found then copy
                    if (resultHelper.Value != null)
                    {
                        // add to the queue
                        queueStructures.Add(new QueueStructure(
                            resultHelper.Value,
                            ReflectionHelper.FetchGetSetProperties(resultHelper.Value.GetType()),
                            sourceValue,
                            ReflectionHelper.FetchGetSetProperties(sourceValue.GetType())));

                        // move on
                        continue;
                    }
                }

                // create the generic object
                object destinationValue = Activator.CreateInstance(destinationListGenericParameterType);

                // were indexers providerd
                if (propertyStructure.IndexMapper != null)
                {
                    // need to create the idnex value so the next lookup has something to check for
                    ListPropertyHandler.AssignIndexValue(
                        sourceValue,
                        destinationValue,
                        propertyStructure.IndexMapper.IndexName);
                }

                // add it to the list
                destinationListAddMethodInfo.Invoke(destinationList, new object[] { destinationValue });

                // add to the queue
                queueStructures.Add(new QueueStructure(
                    destinationValue,
                    ReflectionHelper.FetchGetSetProperties(destinationValue.GetType()),
                    sourceValue,
                    ReflectionHelper.FetchGetSetProperties(sourceValue.GetType())));
            }

            return queueStructures;
        }

        /// <summary/>
        private static ResultHelper FetchMatchingIndexValue(
            object destinationList,
            Type destinationType,
            object sourceValue,
            string indexName)
        {
            PropertyInfo sourceIndexPropertyInfo = ReflectionHelper.FetchGetProperty(
                sourceValue.GetType(),
                indexName);

            // if not found then then move on
            if (sourceIndexPropertyInfo == null)
            {
                return new ResultHelper(false, null);
            }

            // fetch the source
            object sourceIndexValue = ReflectionHelper.FetchPropertyValue(
                sourceIndexPropertyInfo,
                sourceValue);

            // nothing to do if null
            if (sourceIndexValue == null)
            {
                return new ResultHelper(false, null);
            }

            PropertyInfo destinationIndexPropertyInfo = ReflectionHelper.FetchGetProperty(
                destinationType,
                indexName);

            // if not found then then move on
            if (destinationIndexPropertyInfo == null)
            {
                return new ResultHelper(false, null);
            }

            // compare types
            if (destinationIndexPropertyInfo.PropertyType != sourceIndexPropertyInfo.PropertyType)
            {
                return new ResultHelper(false, null);
            }

            // since the object is a list just convert it
            foreach (object destinationValue in (ICollection)destinationList)
            {
                // fetch the destination
                object destinationIndexValue = ReflectionHelper.FetchPropertyValue(
                    destinationIndexPropertyInfo,
                    destinationValue);

                // nothing to do if null
                if (destinationIndexValue == null)
                {
                    return new ResultHelper(false, null);
                }

                // check values ... use equals so the comparison is done on the actual type and not the boxed type
                if (sourceIndexValue.Equals(destinationIndexValue))
                {
                    // return the results
                    return new ResultHelper(true, destinationValue);
                }
            }

            return new ResultHelper(true, null);
        }

        /// <summary/>
        private static void AssignIndexValue(
            object sourceValue,
            object destinationValue,
            string indexName)
        {
            PropertyInfo sourceIndexPropertyInfo = ReflectionHelper.FetchGetProperty(
                sourceValue.GetType(),
                indexName);

            // if not found then then move on
            if (sourceIndexPropertyInfo == null)
            {
                return;
            }

            // fetch the source
            object sourceIndexValue = ReflectionHelper.FetchPropertyValue(
                sourceIndexPropertyInfo,
                sourceValue);

            // nothing to do if null
            if (sourceIndexValue == null)
            {
                return;
            }

            PropertyInfo destinationIndexPropertyInfo = ReflectionHelper.FetchGetProperty(
                destinationValue.GetType(),
                indexName);

            // if not found then then move on
            if (destinationIndexPropertyInfo == null)
            {
                return;
            }

            // nothing to do if null
            if (destinationIndexPropertyInfo == null)
            {
                return;
            }

            // assign the vlaue
            destinationIndexPropertyInfo.SetValue(destinationValue, sourceIndexValue, null);
        }
    }
}
